package worker

import (
	"context"
	"fmt"
	"study-crontab/crontab/common"

	clientv3 "go.etcd.io/etcd/client/v3"
)

//分布式锁(etcdTXN事务)
type JobLock struct {
	kv         clientv3.KV
	lease      clientv3.Lease
	jobName    string             //任务名
	cancelFunc context.CancelFunc //用于终止自动续租
	leaseId    clientv3.LeaseID
	isLocked   bool
}

func NewJobLock(kv clientv3.KV, lease clientv3.Lease, jobName string) *JobLock {
	return &JobLock{
		kv:      kv,
		lease:   lease,
		jobName: jobName,
	}
}

//尝试上锁 (乐观锁)
func (l *JobLock) TryLock() (err error) {
	//1.上锁(创建租约，自动续租，拿着租约去抢占一个key)
	//申请一个lease(租约)
	lease := l.lease
	//申请一个5s的租约
	leaseGrantResp, err := lease.Grant(context.TODO(), 5)
	if err != nil {
		fmt.Println("申请租约失败", err)
		return
	}

	//2.自动续租
	//用于取消自动续租的ctx
	ctx, cancelFunc := context.WithCancel(context.Background())
	//拿到租约的ID
	leaseId := leaseGrantResp.ID

	keepRespChan, err := lease.KeepAlive(ctx, leaseId)
	if err != nil {
		cancelFunc()
		lease.Revoke(context.TODO(), leaseId)
		fmt.Println(err)
		return
	}

	//3.消费续租chan，处理续租应答的协程
	go func() {
		for {
			select {
			case keepResp := <-keepRespChan:
				if keepResp == nil {
					fmt.Println("租约已经失效了")
					return
				} else { //每秒会续租一次
					fmt.Println("收到自动续租应答:", keepResp.ID)
				}
			}
		}
	}()
	//4.创建事务
	txn := l.kv.Txn(context.TODO())
	//锁路径
	lockKey := common.JOB_LOCK_DIR + l.jobName
	//事务抢锁，如果key不存在
	txn.If(clientv3.Compare(clientv3.CreateRevision(lockKey), "=", 0)).
		Then(clientv3.OpPut(lockKey, "lock", clientv3.WithLease(leaseId))).
		Else(clientv3.OpGet(lockKey)) //否则抢锁失败
	//提交事务
	txnResp, err := txn.Commit()
	if err != nil {
		cancelFunc()
		lease.Revoke(context.TODO(), leaseId)
		fmt.Println(err)
		return
	}
	// 判断是否抢到了锁
	if !txnResp.Succeeded {
		cancelFunc()
		lease.Revoke(context.TODO(), leaseId)
		err = common.ERR_LOCK_ALREADY_REQUIRED
		fmt.Println("锁被占用:", string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))
		return
	}
	//2.处理业务
	fmt.Println("处理任务")
	//抢锁成功
	l.leaseId = leaseId
	l.cancelFunc = cancelFunc
	l.isLocked = true
	return
}

//释放锁
func (l *JobLock) Unlock() {
	if l.isLocked {
		l.cancelFunc()
		l.lease.Revoke(context.TODO(), l.leaseId)
	}

}
